// -*-mode:c++; coding:utf-8-*-

#ifndef _IMPHUB_DBP_PROTOCOL_HPP_
#define _IMPHUB_DBP_PROTOCOL_HPP_

#include <sstream>
#include <limits>

#include <boost/shared_ptr.hpp>
#include <boost/cast.hpp>

#include <byteorder.hpp>
#include <binary_iarchive.hpp>
#include <binary_oarchive.hpp>

// 
// protocol packet
// 

enum type_code
{
	QUERY_REQ = 101,
	QUERY_RESP = 102,
	
	INSERT_REQ = 103
};

inline
const char *type_name(type_code c)
{
	switch(c)
	{
	case QUERY_REQ:
		return "QUERY_REQ";
	case QUERY_RESP:
		return "QUERY_RESP";
	case INSERT_REQ:
		return "INSERT_REQ";
	default:
		return "bad type code";
	}
}

static const std::size_t cid_size = 20;
static const std::size_t gcid_size = 20;
static const std::size_t peerid_size = 16;
static const std::size_t filesize_size = sizeof(uint64_t);

static const uint64_t bad_filesize = std::numeric_limits<uint64_t>::max();

struct packet
{
	typedef kb::network_byteorder byteorder;
	typedef kb::binary_iarchive<byteorder> iarchive;
	typedef kb::binary_oarchive<byteorder> oarchive;

	static const std::size_t header_size = sizeof(uint32_t) * 10;
	static const std::size_t buffer_size = 1024 * 1024; // 1M

	static const std::size_t pos_length = sizeof(uint32_t) * 4;

	uint32_t m_version;	// 100
	uint32_t m_sign;	// = 99887766
	uint32_t m_seq;
	uint32_t m_flag;
	uint32_t m_length;	// include header length

	uint32_t m_reserve[4];	// 16 bytes

	uint32_t m_type;

	type_code type() const {
		return static_cast<type_code>(m_type);
	}

	packet(type_code t,
	       std::size_t seq)
		: m_version(100)
		, m_sign(99887766)
		, m_seq(seq)
		, m_flag(0)
		, m_length(0)
		, m_type(t) {}

	virtual
	~packet() {}

	template<typename IArchive>
	inline
	bool load(IArchive &in) {
		return in.load(&m_version) &&
			in.load(&m_sign) &&
			in.load(&m_seq) &&
			in.load(&m_flag) &&
			in.load(&m_length) &&
			in.seek(sizeof(uint32_t) * 4,
				SEEK_CUR) &&
			in.load(&m_type);
	}

	template<typename OArchive>
	inline
	bool save(OArchive &out) const {
		return out.save(m_version) &&
			out.save(m_sign) &&
			out.save(m_seq) &&
			out.save(m_flag) &&
			out.save(m_length) &&
			out.seek(sizeof(uint32_t) * 4,
				 SEEK_CUR) &&
			out.save(m_type);
	}

	virtual
	std::string to_string() const;
};

typedef boost::shared_ptr<packet> packet_ptr;

template<typename T>
class basic_packet
	: public packet
{
public:
	typedef basic_packet<T> this_type;
			
	typedef T value_type;
	//typedef T element_type;
	
	// get type code by type code tag or user defined tag
	static const type_code type = T::type;

private:
	T m_body;

public:
	explicit
	basic_packet(std::size_t sequence_number = 0)
		: packet(type,
			 sequence_number)
		, m_body() {}
	
	template<typename IArchive>
	bool load(IArchive &in) {
		return packet::load(in) &&
			m_body.load(in);
	}
	
	template<typename OArchive>
	bool save(OArchive &out) const {
		char * const buf = out.left_buffer();
		const std::size_t buf_size = out.left_buffer_size();
		const bool ok = packet::save(out) &&
			m_body.save(out);
		if(ok)
		{
			const std::size_t length = buf_size - out.left_buffer_size();
			const uint32_t len = static_cast<uint32_t>(length);
			char * const data_length_buf = buf + packet::pos_length;
			OArchive out_length(data_length_buf, sizeof(len));
			out_length << len;
		}
		return ok;
	}

	T &body() {
		return m_body;
	}

	const T &body() const {
		return m_body;
	}

	virtual
	std::string to_string() const {
		std::ostringstream out;
		out << "{header: " << packet::to_string()
		    << "; body: " << m_body.to_string()
		    << "}";
		return out.str();
	}
};

struct insert_request
{
	static const type_code type = INSERT_REQ;

	std::string m_cid;
	std::string m_gcid;
	uint64_t m_filesize;
	std::vector<std::string> m_peerid_list;

	template<typename IArchive>
	inline
	bool load(IArchive &in) {
		return in.load(&m_cid) &&
			in.load(&m_gcid) &&
			in.load(&m_filesize) &&
			in.load(&m_peerid_list);
	}

	template<typename OArchive>
	inline
	bool save(OArchive &out) const {
		return out.save(m_cid) &&
			out.save(m_gcid) &&
			out.save(m_filesize) &&
			out.save(m_peerid_list);
	}

	std::string to_string() const;
};

typedef boost::shared_ptr< basic_packet<insert_request> > insert_request_ptr;

struct query_request
{
	static const type_code type = QUERY_REQ;
	
	std::string m_cid;
	uint32_t m_limit_offset;
	uint32_t m_limit_count;

	template<typename IArchive>
	inline
	bool load(IArchive &in) {
		return in.load(&m_cid) &&
			in.load(&m_limit_offset) &&
			in.load(&m_limit_count);
	}

	template<typename OArchive>
	inline
	bool save(OArchive &out) const {
		return out.save(m_cid) &&
			out.save(m_limit_offset) &&
			out.save(m_limit_count);
	}

	std::string to_string() const;
};

typedef boost::shared_ptr< basic_packet<query_request> > query_request_ptr;

struct peer_id
{
	std::string m_cid;
	std::string m_gcid;
	uint64_t m_filesize;
	std::string m_peerid;

	template<typename IArchive>
	inline
	bool load(IArchive &in) {
		return in.load(&m_cid) &&
			in.load(&m_gcid) &&
			in.load(&m_filesize) &&
			in.load(&m_peerid);
	}

	template<typename OArchive>
	inline
	bool save(OArchive &out) const {
		return out.save(m_cid) &&
			out.save(m_gcid) &&
			out.save(m_filesize) &&
			out.save(m_peerid);
	}

	std::string to_string() const;
};

struct query_response
	: public packet
{
	static const type_code type = QUERY_RESP;
	
	uint32_t m_result;	// 0 for success, otherwise fail
	std::vector<peer_id> m_peerid_list;

	template<typename IArchive>
	inline
	bool load(IArchive &in) {
		bool ok = in.load(&m_result);
		if(m_result == 0)
			ok = in.load(&m_peerid_list);
		return ok;
	}

	template<typename OArchive>
	inline
	bool save(OArchive &out) const {
		bool ok = out.save(m_result);
		if(m_result == 0)
			ok = out.save(m_peerid_list);
		return ok;
	}

	std::string to_string() const;
};

typedef boost::shared_ptr< basic_packet<query_response> > query_response_ptr;

template<typename IArchive>
inline
packet_ptr load(IArchive &in, type_code type)
{
	packet_ptr ptr;
	switch(type)
	{
	case QUERY_REQ:
	{
		query_request_ptr req(new query_request);
		if(req->load(in))
			ptr = req;
		break;
	}
	case QUERY_RESP:
	{
		query_response_ptr resp(new query_response);
		if(resp->load(in))
			ptr = resp;
		break;
	}
	case INSERT_REQ:
	{
		insert_request_ptr req(new insert_request);
		if(req->load(in))
			ptr = req;
		break;
	}
	default:
		break;
	}
	return ptr;
}

template<typename OArchive>
inline
bool save(OArchive &out, const packet &ptr)
{
	bool ok = false;
	switch(ptr.type())
	{
	case QUERY_REQ:
	{
		const basic_packet<query_request> &req =
			*boost::polymorphic_downcast<const basic_packet<query_request>*>(&ptr);
		ok = req.save(out);
		break;
	}
	case QUERY_RESP:
	{
		const basic_packet<query_response> &resp =
			*boost::polymorphic_downcast<const basic_packet<query_response>*>(&ptr);
		ok = resp.save(out);
		break;
	}
	case INSERT_REQ:
	{
		const basic_packet<insert_request> &req =
			*boost::polymorphic_downcast<const basic_packet<insert_request>*>(&ptr);
		ok = req.save(out);
		break;
	}
	default:
		break;
	}
	return ok;
}

#endif	// _IMPHUB_DBP_PROTOCOL_HPP_
